#

Introducción

En el presente estudio se procederá a analizar el data set llamado “Netflix Prize data” obtenido en la página web Kaggle. Dicho dataset se creó como una competición para encontrar el mejor algoritmo de predicción de la puntuación que los usuarios dan a las películas de la plataforma.

Antes de extraer la información y comenzar a trabajar sobre ella se ha procedido a visualizar a qué tipo de datos íbamos a enfrentarnos. El dataset contiene diversos archivos pero solo nos centraremos con los que vamos a trabajar en este estudio:

Combined Data

Los archivos ‘combined_data_{nº}’ contienen la información de las puntuaciones de los usuarios para determinado código de película, así como el identificador de usuario y la fecha (día, mes y año) en la que se dio dicha valoración.

Esta base de datos cuenta con un tamaño de espacio en memoria demasiado elevado, por lo que se procederá a extraer los datos de 250 películas escogidas de manera aleatoria con una semilla (“2495”).

Movie titles

Otro de los archivos que se utilizarán en este estudio contiene el título de cada ID asociado a las películas además del año en el que se estrenó. En secciones posteriores explicaremos como realizaremos la inclusión de estos datos junto con los datos iniciales para obtener el dataset final con el que trabajaremos de forma más cómoda.

Una vez realizado este proceso generaremos una nueva variable para incluirla en nuestro estudio ya que sería interesante medir la diferencia de años transcurridos desde el estreno hasta la puntuación de las diferentes películas (“YearsSinceRelease”).

Carga y lectura de datos

El primer paso para realizar la carga de datos es leer el fichero “filas_ID_combined_all.txt” de donde obtendremos por cada ID de película el número del fichero donde se encuentran las puntuaciones de cada usuario y las posiciones de la fila inicial y final de estas. De la misma forma, cargaremos los cuatro ficheros ‘combined_data_{nº}’ con la información de las puntuaciones de las películas.

Ya que el fichero “filas_ID_combined_all.txt” cuenta con la información de todas las películas, después de exportarlo realizaremos un filtrado para quedarnos solo con las filas de las 250 películas que nos interesan estudiar. Seguidamente, utilizaremos la función obtain_movies para que, pasados los parámetros del fichero “filas_ID_combined_all.txt” y el ID de ‘combined_data_{nº}’, calculemos todas las posiciones a leer y nos devuelva un combined_data con solo las observaciones de las películas que sabemos que se encuentran en dicho fichero.

#Función que obtiene los datos de filas específicas
obtain_movies = function(index_data, combined_data, idx){
  #Obtenemos las películas del archivo a leer (1,2,3 o 4)
  rows_data = filter(index_data, data == idx)
  rows_data = select(rows_data, fila, fila_final)
  
  #Por cada fila guardamos el rango de posiciones a leer en el fichero
  ranges = c()
  for(i in 1:nrow(rows_data)){
    ranges = c(ranges, rows_data[[i,1]]:rows_data[[i,2]])
    }
  
  #Nos quedamos solo con los datos de las películas de la muestra
  combined_data = slice(combined_data, ranges)
  return(combined_data)
}
#Leemos el índice
filas_ID_combined_all = read_csv("data/filas_ID_combined_all.txt")

#Cargamos los ficheros con la información de Netflix
combined_data1 = read_tsv("data/combined_data_1.txt", col_names=FALSE)
combined_data2 = read_tsv("data/combined_data_2.txt", col_names=FALSE)
combined_data3 = read_tsv("data/combined_data_3.txt", col_names=FALSE)
combined_data4 = read_tsv("data/combined_data_4.txt", col_names=FALSE)

#Obtenemos nuestra muestra de películas
n_muestra = 250
set.seed(2495)
muestra_grupo = sample(1:17770, n_muestra, replace = FALSE)

#Nos quedamos con el índice de las películas de nuestra muestra
filas_ID_combined_all = filter(filas_ID_combined_all, filas_ID_combined_all$ID %in% muestra_grupo)
filas_ID_combined_all = select(filas_ID_combined_all, ID, fila, fila_final, data)

#Mostramos cuántas películas debemos leer de cada fichero
#table(filas_ID_combined_all$data)

#Leemos las observaciones de las películas de nuestra muestra
data1 = obtain_movies(filas_ID_combined_all, combined_data1, 1)
data2 = obtain_movies(filas_ID_combined_all, combined_data2, 2)
data3 = obtain_movies(filas_ID_combined_all, combined_data3, 3)
data4 = obtain_movies(filas_ID_combined_all, combined_data4, 4)

#Juntamos los datos de las películas de los 4 ficheros
df_ratings = rbind(data1,data2,data3,data4)

#Exportamos los datos de las películas de nuestra muestra
write.table(df_ratings,"data/combined_data_sample.txt", row.names = FALSE, col.names = FALSE)

#Eliminamos variables que ya no son útiles
rm(combined_data1, combined_data2, combined_data3, combined_data4, data1, data2, data3, data4, filas_ID_combined_all)

Construcción del modelo de datos

Una vez hemos exportado los ficheros necesarios y filtrado las observaciones de interés, procedemos a la limpieza de datos y construcción del modelo de datos final. Generamos un dataframe que por cada observación contenga: ID de la película, ID de usuario, calificación y fecha de calificación.

#Se asigna una posición a cada observación para posteriormente indicar el id de película de cada una de ellas
df_ratings = df_ratings %>% 
             mutate(Idx = row_number()) 

#Guardamos la fila donde empieza cada película
movie_rows = grep(":", df_ratings$X1) 

#Añadimos el id de la película a cada posición y eliminamos el caracter ":"
rows_ID = df_ratings %>% 
          filter(Idx %in% movie_rows) %>%
          mutate(X1 = as.integer(gsub(":","",X1)))

#Número de veces que se tendrá que repetir el identificador de cada película
reps = diff(c(rows_ID$Idx, max(df_ratings$Idx) + 1))
netflix = df_ratings %>% 
     mutate(MovieID = rep(rows_ID$X1, times = reps)) %>% 
     filter(!(Idx %in% rows_ID))

#Se definen las columnas del dataframe
netflix = netflix %>% 
     separate(X1,into = c("UserID","Rating","RatingDate"), sep = ",") %>%
     na.omit(netflix) %>%
     mutate(Idx = row_number())

#Se eliminan las variables auxiliares
rm(df_ratings, movie_rows, rows_ID, reps)

Con el objectivo de completar nuestro dataset, realizamos la unión de la tibble netflix con los datos exportados del fichero movies_titles.csv, añadiendo así la variable del nombre de la película y el año de estreno. Una vez se ha realizado el inner join, se procede al cambio de tipo de cada variable para que concuerde con la información que representan.

#Lectura del fichero movie_titles.csv
df_movies = read_tsv("data/movie_titles.csv", locale = readr::locale(encoding = "ISO-8859-1"), col_names=FALSE)

df_movies = df_movies %>% 
            separate(X1,",",into =c("MovieID","MovieRelease","Title"), extra="merge")

df_movies = filter(df_movies, df_movies$MovieID %in% muestra_grupo)


#Se transforma la variable MovieRelease a numérica
netflix$MovieID = as.character(netflix$MovieID)

#Se unifica el dataframe de las puntuaciones con el de las películas
netflix = inner_join(x = netflix, y = df_movies, by = "MovieID", all = TRUE)

#Se transforma la variable 'Rating' a tipo númerica
netflix$Rating = as.numeric(netflix$Rating)

#Se transforma la variable 'RatingDate' a tipo date
netflix$RatingDate = as.Date(netflix$RatingDate, format = "%Y-%m-%d")

#Se transforma la variable MovieRelease a numérica
netflix$MovieRelease = as.numeric(netflix$MovieRelease)

#Se añade la diferencia en años entre el año de puntuación y el de estreno de la película
netflix = mutate(netflix, YearsSinceRelease = year(RatingDate) - MovieRelease)

#Se transforma la variable YearsSinceRelease a numérica
netflix$YearsSinceRelease = as.numeric(netflix$YearsSinceRelease)

#Adicionalmente, dividimos la variable RatingDate en: día, mes y año
sep = netflix %>%
  separate(RatingDate,into = c("Year","Month","Day"), sep = "-") %>%
  na.omit(netflix) %>%
  mutate(Idx = row_number())

netflix = mutate(netflix, sep)
rm(sep)

#Se ordenan las posiciones de las columnas y se indican su nuevo nombre
netflix = select(netflix, Idx, MovieID, Title, UserID, Rating, Day, Month, Year, RatingDate, MovieRelease,  YearsSinceRelease)

#Exportamos el fichero preparado y construido para empezar a trabajar en su análisis
write.csv(netflix, "data/netflix.csv", row.names = FALSE)

Analizando nuestros datos

Una vez tenemos la tibble final sobre la que vamos a trabajar, procedemos a estudiar su estructura:

## tibble [1,093,856 x 11] (S3: tbl_df/tbl/data.frame)
##  $ Idx              : int [1:1093856] 1 2 3 4 5 6 7 8 9 10 ...
##  $ MovieID          : chr [1:1093856] "43" "43" "43" "43" ...
##  $ Title            : chr [1:1093856] "Silent Service" "Silent Service" "Silent Service" "Silent Service" ...
##  $ UserID           : chr [1:1093856] "305151" "497196" "2327803" "2625420" ...
##  $ Rating           : num [1:1093856] 4 3 1 2 5 2 1 3 5 2 ...
##  $ Day              : chr [1:1093856] "20" "13" "19" "13" ...
##  $ Month            : chr [1:1093856] "02" "04" "08" "05" ...
##  $ Year             : chr [1:1093856] "2005" "2003" "2001" "2004" ...
##  $ RatingDate       : Date[1:1093856], format: "2005-02-20" "2003-04-13" ...
##  $ MovieRelease     : num [1:1093856] 2000 2000 2000 2000 2000 2000 2000 2000 2000 2000 ...
##  $ YearsSinceRelease: num [1:1093856] 5 3 1 4 3 3 5 2 3 3 ...
##  - attr(*, "na.action")= 'omit' Named int [1:250] 1 107 293 1390 3766 4458 5563 5738 8145 8489 ...
##   ..- attr(*, "names")= chr [1:250] "1" "107" "293" "1390" ...
  • Idx: Variable numérica que determina la posición o índice de cada observación del dataset.

  • MovieID: Variable [TODO] que identifica a cada película con un ID determinado, este ID está asociado a un título que será representado por la variable “Title”.

  • Title: Variable con datos definidos como caracteres. Esta variable representa el título de cada una de las películas identificadas con un ID en la variable “MovieID”.

  • UserID: Variable con datos definidos como caracteres que representa el ID único del usuario que ha calificado la película.

  • Rating: Variable numérica ordinal que representa la calificación que ha hecho cada usuario de las películas que ha puntuado. Esta calificación puede ser dentro del rango [1,2,3,4,5], donde 1 estrella representa la calificación más baja y 5 estrellas la calificación más alta.

  • RatingDate: Variable definida como tipo **date*, que representa la fecha en la cual el usuario realizó la calificación de una determinada película.

  • MovieRelease: Variable numérica que indica el año en el cual se estrenó la película. El año de estreno puede referirse tanto a estreno de una determinada película en el cine como en DVD.

  • YearsSinceRelease: Variable numérica la cual se ha calculado mediante la resta de RatingDate y MovieRelease. Esta variable indica el número de años que ha pasado desde que se ha estrenado una determinada película hasta que el usuario la ha calificado.

Calificaciones de las películas

Calificación general

Analizar número de votaciones de las diferentes películas. ¿Cuántas películas hay de cada rating (1,2,3,4 y 5 estrellas)?

ggplot(data = netflix, aes(x = Rating)) +
  geom_bar(aes(y = ..count.., fill = ..count..), 
           stat="count", 
           show.legend = FALSE) +
  geom_label(aes(label = ..count.., y = ..count..), 
            stat = "count",
            vjust = -.5) +
  scale_fill_gradient(low = "lightcoral", high = "firebrick2")+
  labs(x = "Rating", y = "Number of ratings", title = "Total Ratings")+
  scale_y_continuous(limits=c(0,380000), labels = scales::comma)+
  coord_flip() + 
  theme_classic()

# Image in the visualization 
image = image_read("imgs/icon-rating.png") 
grid.raster(image, x = 0.80, y = 0.25, height = 0.23)

Como se puede observar en el gráfico, las puntuaciones generalmente han sido positivas ya que la mayoria de los casos tienen una puntuación superior a 3.

La calificación de 4 que ha sido dada por un total de 2926 usuarios, es la que se ha dado más veces.

La calificación media de todas las películas es de 3.37.

Distribución de calificaciones por año

df_movies = filter(df_movies, df_movies$MovieID %in% muestra_grupo)
calif_año = table(df_movies$MovieRelease)
df_movies$MovieRelease = as.numeric(df_movies$MovieRelease)

break_range = seq(from = 1920, to = 2010, by = 10)

hist_plot1 = ggplot(data = df_movies) +
  geom_histogram(aes(x = MovieRelease, y = ..count../sum(..count..)),
                 breaks = break_range,
                 color = c("darkgoldenrod2", "lightgoldenrod1", "lightblue", "palegreen3", 
                           "lightpink2", "plum3", "steelblue3", "darkolivegreen3", "salmon3"),
                 fill = c("darkgoldenrod1", "khaki1", "lightblue", "palegreen", "lightpink1", 
                          "plum2", "steelblue1", "darkolivegreen2", "salmon1"),
                 size = 1) +
  geom_density(aes(x = MovieRelease, y = 10*..density..),
               color = "black",
               fill = "seashell3",
               size = 1, 
               alpha = 0.2,
               show.legend = FALSE) +
  scale_x_continuous(breaks = break_range) +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1), 
                     limits = c(0, 0.5)) +
  scale_color_gradient(low = "indianred1", high = "red2") +
  ylab("% of movies") +
  xlab("Movie release's decade") +
  ggtitle("Percentage of movies released by decade") +
  theme_minimal()

div(ggplotly(hist_plot1), align = "center")

Distribución de valoraciones por mes

bar_plot2 = ggplot(data = netflix) +
  geom_bar(aes(x = Month, y = ..count../sum(..count..), fill = ..count../sum(..count..)),
           show.legend = FALSE,
           color = c("darkslategray3", "darkslategray3", "darkslategray3", "lightpink2", 
                     "lightpink2", "lightpink2", "lightgoldenrod2", "lightgoldenrod2", "lightgoldenrod2",
                     "tan2", "tan2", "tan2"),
           fill = c("darkslategray2", "darkslategray2", "darkslategray2", "lightpink1", 
                     "lightpink1", "lightpink1", "lightgoldenrod1", "lightgoldenrod1", "lightgoldenrod1",
                     "tan1", "tan1", "tan1"),
           size = 1) +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1), 
                     limits = c(0, 0.15)) +
  scale_x_discrete(labels = month.abb) +
  ylab("% Ratings") +
  xlab("Rating month") +
  ggtitle("Percentage of ratings by month")
  
div(ggplotly(bar_plot2), align = "center")

Distribución de valoraciones por día

netflix$DayOfWeek = weekdays(netflix$RatingDate)

bar_plot3 = ggplot(data = netflix) +
  geom_bar(aes(x = DayOfWeek, y = ..count../sum(..count..), fill = ..count../sum(..count..)),
           show.legend = FALSE,
           color = c("indianred4", "lightgoldenrod2", "palevioletred2", "palegreen4", 
                     "tan2", "steelblue3", "mediumorchid4"),
           fill = c("indianred3", "lightgoldenrod1", "palevioletred1", "palegreen3", 
                     "tan1", "steelblue2", "mediumorchid3"),
           size = 1) +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1), 
                     limits = c(0, 0.25)) +
  scale_x_discrete(labels = month.abb) +
  ylab("% Ratings") +
  xlab("Rating day of week") +
  ggtitle("Percentage of ratings by day of week")
  
div(ggplotly(bar_plot3), align = "center")

Películas más votadas

movies_table_title = sort(table(netflix$Title), decreasing = TRUE)
movies_table_title = as.data.frame(movies_table_title)

set.seed(2495)
ggplot(movies_table_title, aes(label = Var1, size = Freq, color = Freq, angle = sample(c(0,15,30, 45,60, 75,90, 105,120,135, 160), 250, replace = TRUE))) +
  geom_text_wordcloud_area(mask = png::readPNG("imgs/netflix-mask2.png"),
    rm_outside = TRUE) +
  scale_color_gradient(low = "indianred1", high = "red2") +
  theme(panel.background = element_rect(fill = "black"))

[TODO]

Tabla resumen del número de calificaciones

import pandas as pd
netflix_py = pd.read_csv("data/netflix.csv")
res = pd.concat([netflix_py.groupby('MovieID')['Rating'].describe(),
                      netflix_py.groupby('MovieID')['Rating'].agg(pd.Series.mode).rename('mode'),
                      netflix_py.groupby('MovieID')['Rating'].agg(pd.Series.median).rename('median')
                     ], axis=1).T
                     
print(res)
## MovieID       43          195    ...      17606        17751
## count    105.000000  185.000000  ...  87.000000  2475.000000
## mean       2.571429    3.297297  ...   2.229885     3.938182
## std        1.284951    1.039044  ...   1.075100     1.133302
## min        1.000000    1.000000  ...   1.000000     1.000000
## 25%        1.000000    3.000000  ...   1.000000     3.000000
## 50%        3.000000    3.000000  ...   2.000000     4.000000
## 75%        3.000000    4.000000  ...   3.000000     5.000000
## max        5.000000    5.000000  ...   5.000000     5.000000
## mode       3.000000    3.000000  ...   1.000000     5.000000
## median     3.000000    3.000000  ...   2.000000     4.000000
## 
## [10 rows x 250 columns]

Las 10 películas más votadas

movies_table = table(netflix$MovieID)

#Ordenamos las MovieID en función del número de calificaciones y de forma descendente:
orden = sort(movies_table, decreasing = TRUE)


#Tabla agrupada por película y año del número de valoraciones.
tabla = table(netflix$MovieID, netflix$Year)
tabla = as.data.frame(tabla)
tabla = filter(tabla, Var1 %in% names(orden[1:10]))
#Representación gráfica.
ggplot(tabla, aes(x = Var2, y = Freq, group = Var1))+
geom_line()

#Compara sus estadísticos y distribuciones(histogramas, boxplot, violin plot,. . . )

#Distribución del score promedio por año.
a = filter (netflix, MovieID %in% names(orden[1:10]))

res =  a %>%
  group_by(MovieID, Year) %>%
  summarise(mean = mean(Rating))
## `summarise()` regrouping output by 'MovieID' (override with `.groups` argument)

Comparación de las 5 películas con mayor número de calificaciones

#Pedimos que nos imprima las 5 primeras observaciones, para obtener asi las 5 películas más votadas:
head(orden, 5)
## 
##  6408  2580  3610  2186 11663 
## 82949 80136 75148 61174 54845
#Una vez sabemos el MovieID de las películas más votadas las filtramos:
mas_votadas = filter(netflix, MovieID == names(orden[1:5]))
## Warning in MovieID == names(orden[1:5]): longitud de objeto mayor no es múltiplo
## de la longitud de uno menor
mas_votadas = as.data.frame(mas_votadas)

#Representamos
boxplot = ggplot(mas_votadas,aes(Title, Rating, group=MovieID))+
  geom_boxplot(fill = "darkseagreen1", colour = "black", outlier.colour = "darkgreen")+
   geom_point(stat= "summary", shape=20, size=2, color="red") 
ggplotly(boxplot)
## No summary function supplied, defaulting to `mean_se()`
## Warning: `group_by_()` is deprecated as of dplyr 0.7.0.
## Please use `group_by()` instead.
## See vignette('programming') for more help
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_warnings()` to see where this warning was generated.

Película con mejor y peor calificación (gráfico palabras)

Película más votada y menos votada (gráfico palabras)

#Más votada
df_more_voted = netflix[netflix$MovieID == names(which.max(movies_table)),]

ggplot(data = df_more_voted, aes(x = Rating)) +
  geom_bar(aes(y = ..count.., fill = ..count..), 
           stat="count", 
           show.legend = FALSE) +
  geom_label(aes(label = ..count.., y = ..count..), 
            stat = "count",
            vjust = -.5) +
  scale_fill_gradient(low = "cadetblue1", high = "cadetblue4")+
  labs(x = "Rating", y = "Number of ratings", title = df_more_voted$Title[1])+
  scale_y_continuous(limits=c(0,45000))+
  coord_flip() + 
  theme_classic()

#Image in the visualization 
image = image_read("imgs/masvotada.jpg") 
grid.raster(image, x = 0.80, y = 0.35, height = 0.4)

#Menos votada
df_less_voted = netflix[netflix$MovieID == names(which.min(movies_table)),]

ggplot(data = df_less_voted, aes(x = Rating)) +
  geom_bar(aes(y = ..count.., fill = ..count..), 
           stat="count", 
           show.legend = FALSE) +
  geom_label(aes(label = ..count.., y = ..count..), 
            stat = "count",
            vjust = -.5) +
  scale_fill_gradient(low = "darkgoldenrod1", high = "darkgoldenrod3")+
  labs(x = "Rating", y = "Number of ratings", title = df_less_voted$Title[1])+
  scale_y_continuous(limits=c(0,8))+
  coord_flip() + 
  theme_classic()

# Image in the visualization 
image = image_read("imgs/menosvotada.jpg") 
grid.raster(image, x = 0.80, y = 0.35, height = 0.4)

Analizar nº de votaciones/puntuación de cada película

Relación fecha puntuación / calificación

¿Cuántas puntuaciones hay por cada año?

Media del rating por año

Relación fecha release de la película / calificación

Relación fecha película con la fecha puntuación y la puntuación

¿Si las fechas de release y puntuación son cercanas, la puntuación es mejor o peor? Revisar la diferencia de las fechas

Tiempos

Tiempo medio que ha pasado desde el estreno de la película hasta que ha sido puntuada

Cantidad de tiempo entre estreno y calificación (Nº de casos)

#